package sample.context.lock; import java.io.Serializable; import java.util.*; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Supplier; import sample.InvocationException; /** * ID単位のロックを表現します。 * low: ここではシンプルに口座単位のIDロックのみをターゲットにします。 * low: 通常はDBのロックテーブルに"for update"要求で悲観的ロックをとったりしますが、サンプルなのでメモリロックにしてます。 */ public class IdLockHandler { private Map<Serializable, ReentrantReadWriteLock> lockMap = new HashMap<>(); /** IDロック上で処理を実行します。 */ public void call(Serializable id, LockType lockType, final Runnable command) { call(id, lockType, () -> { command.run(); return true; }); } public <T> T call(Serializable id, LockType lockType, final Supplier<T> callable) { if (lockType.isWrite()) { writeLock(id); } else { readLock(id); } try { return callable.get(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new InvocationException("error.Exception", e); } finally { unlock(id); } } private void writeLock(final Serializable id) { Optional.of(id).ifPresent((v) -> { synchronized (lockMap) { idLock(v).writeLock().lock(); } }); } private ReentrantReadWriteLock idLock(final Serializable id) { if (!lockMap.containsKey(id)) { lockMap.put(id, new ReentrantReadWriteLock()); } return lockMap.get(id); } public void readLock(final Serializable id) { Optional.of(id).ifPresent((v) -> { synchronized (lockMap) { idLock(v).readLock().lock(); } }); } public void unlock(final Serializable id) { Optional.of(id).ifPresent((v) -> { synchronized (lockMap) { ReentrantReadWriteLock idLock = idLock(v); if (idLock.isWriteLockedByCurrentThread()) { idLock.writeLock().unlock(); } else { idLock.readLock().unlock(); } } }); } /** * ロック種別を表現するEnum。 */ public static enum LockType { /** 読み取り専用ロック */ Read, /** 読み書き専用ロック */ Write; public boolean isRead() { return !isWrite(); } public boolean isWrite() { return this == Write; } } }